home *** CD-ROM | disk | FTP | other *** search
/ PC go! 2018 July / PCgo 07-2018 CD-ROM Germany.iso / nw.pak / Unnamed File 000154.txt < prev    next >
Encoding:
Text File  |  2015-07-29  |  18.4 KB  |  694 lines

  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4.  
  5. cr.define('cr.ui', function() {
  6.   // require cr.ui.define
  7.   // require cr.ui.limitInputWidth
  8.  
  9.   /**
  10.    * The number of pixels to indent per level.
  11.    * @type {number}
  12.    * @const
  13.    */
  14.   var INDENT = 20;
  15.  
  16.   /**
  17.    * Returns the computed style for an element.
  18.    * @param {!Element} el The element to get the computed style for.
  19.    * @return {!CSSStyleDeclaration} The computed style.
  20.    */
  21.   function getComputedStyle(el) {
  22.     return el.ownerDocument.defaultView.getComputedStyle(el);
  23.   }
  24.  
  25.   /**
  26.    * Helper function that finds the first ancestor tree item.
  27.    * @param {Node} node The node to start searching from.
  28.    * @return {cr.ui.TreeItem} The found tree item or null if not found.
  29.    */
  30.   function findTreeItem(node) {
  31.     while (node && !(node instanceof TreeItem)) {
  32.       node = node.parentNode;
  33.     }
  34.     return node;
  35.   }
  36.  
  37.   /**
  38.    * Creates a new tree element.
  39.    * @param {Object=} opt_propertyBag Optional properties.
  40.    * @constructor
  41.    * @extends {HTMLElement}
  42.    */
  43.   var Tree = cr.ui.define('tree');
  44.  
  45.   Tree.prototype = {
  46.     __proto__: HTMLElement.prototype,
  47.  
  48.     /**
  49.      * Initializes the element.
  50.      */
  51.     decorate: function() {
  52.       // Make list focusable
  53.       if (!this.hasAttribute('tabindex'))
  54.         this.tabIndex = 0;
  55.  
  56.       this.addEventListener('click', this.handleClick);
  57.       this.addEventListener('mousedown', this.handleMouseDown);
  58.       this.addEventListener('dblclick', this.handleDblClick);
  59.       this.addEventListener('keydown', this.handleKeyDown);
  60.     },
  61.  
  62.     /**
  63.      * Returns the tree item that are children of this tree.
  64.      */
  65.     get items() {
  66.       return this.children;
  67.     },
  68.  
  69.     /**
  70.      * Adds a tree item to the tree.
  71.      * @param {!cr.ui.TreeItem} treeItem The item to add.
  72.      */
  73.     add: function(treeItem) {
  74.       this.addAt(treeItem, 0xffffffff);
  75.     },
  76.  
  77.     /**
  78.      * Adds a tree item at the given index.
  79.      * @param {!cr.ui.TreeItem} treeItem The item to add.
  80.      * @param {number} index The index where we want to add the item.
  81.      */
  82.     addAt: function(treeItem, index) {
  83.       this.insertBefore(treeItem, this.children[index]);
  84.       treeItem.setDepth_(this.depth + 1);
  85.     },
  86.  
  87.     /**
  88.      * Removes a tree item child.
  89.      * @param {!cr.ui.TreeItem} treeItem The tree item to remove.
  90.      */
  91.     remove: function(treeItem) {
  92.       this.removeChild(treeItem);
  93.     },
  94.  
  95.     /**
  96.      * The depth of the node. This is 0 for the tree itself.
  97.      * @type {number}
  98.      */
  99.     get depth() {
  100.       return 0;
  101.     },
  102.  
  103.     /**
  104.      * Handles click events on the tree and forwards the event to the relevant
  105.      * tree items as necesary.
  106.      * @param {Event} e The click event object.
  107.      */
  108.     handleClick: function(e) {
  109.       var treeItem = findTreeItem(/** @type {!Node} */(e.target));
  110.       if (treeItem)
  111.         treeItem.handleClick(e);
  112.     },
  113.  
  114.     handleMouseDown: function(e) {
  115.       if (e.button == 2) // right
  116.         this.handleClick(e);
  117.     },
  118.  
  119.     /**
  120.      * Handles double click events on the tree.
  121.      * @param {Event} e The dblclick event object.
  122.      */
  123.     handleDblClick: function(e) {
  124.       var treeItem = findTreeItem(/** @type {!Node} */(e.target));
  125.       if (treeItem)
  126.         treeItem.expanded = !treeItem.expanded;
  127.     },
  128.  
  129.     /**
  130.      * Handles keydown events on the tree and updates selection and exanding
  131.      * of tree items.
  132.      * @param {Event} e The click event object.
  133.      */
  134.     handleKeyDown: function(e) {
  135.       var itemToSelect;
  136.       if (e.ctrlKey)
  137.         return;
  138.  
  139.       var item = this.selectedItem;
  140.       if (!item)
  141.         return;
  142.  
  143.       var rtl = getComputedStyle(item).direction == 'rtl';
  144.  
  145.       switch (e.keyIdentifier) {
  146.         case 'Up':
  147.           itemToSelect = item ? getPrevious(item) :
  148.               this.items[this.items.length - 1];
  149.           break;
  150.         case 'Down':
  151.           itemToSelect = item ? getNext(item) :
  152.               this.items[0];
  153.           break;
  154.         case 'Left':
  155.         case 'Right':
  156.           // Don't let back/forward keyboard shortcuts be used.
  157.           if (!cr.isMac && e.altKey || cr.isMac && e.metaKey)
  158.             break;
  159.  
  160.           if (e.keyIdentifier == 'Left' && !rtl ||
  161.               e.keyIdentifier == 'Right' && rtl) {
  162.             if (item.expanded)
  163.               item.expanded = false;
  164.             else
  165.               itemToSelect = findTreeItem(item.parentNode);
  166.           } else {
  167.             if (!item.expanded)
  168.               item.expanded = true;
  169.             else
  170.               itemToSelect = item.items[0];
  171.           }
  172.           break;
  173.         case 'Home':
  174.           itemToSelect = this.items[0];
  175.           break;
  176.         case 'End':
  177.           itemToSelect = this.items[this.items.length - 1];
  178.           break;
  179.       }
  180.  
  181.       if (itemToSelect) {
  182.         itemToSelect.selected = true;
  183.         e.preventDefault();
  184.       }
  185.     },
  186.  
  187.     /**
  188.      * The selected tree item or null if none.
  189.      * @type {cr.ui.TreeItem}
  190.      */
  191.     get selectedItem() {
  192.       return this.selectedItem_ || null;
  193.     },
  194.     set selectedItem(item) {
  195.       var oldSelectedItem = this.selectedItem_;
  196.       if (oldSelectedItem != item) {
  197.         // Set the selectedItem_ before deselecting the old item since we only
  198.         // want one change when moving between items.
  199.         this.selectedItem_ = item;
  200.  
  201.         if (oldSelectedItem)
  202.           oldSelectedItem.selected = false;
  203.  
  204.         if (item) {
  205.           item.selected = true;
  206.           if (item.id)
  207.             this.setAttribute('aria-activedescendant', item.id);
  208.         } else {
  209.             this.removeAttribute('aria-activedescendant');
  210.         }
  211.         cr.dispatchSimpleEvent(this, 'change');
  212.       }
  213.     },
  214.  
  215.     /**
  216.      * @return {!ClientRect} The rect to use for the context menu.
  217.      */
  218.     getRectForContextMenu: function() {
  219.       // TODO(arv): Add trait support so we can share more code between trees
  220.       // and lists.
  221.       if (this.selectedItem)
  222.         return this.selectedItem.rowElement.getBoundingClientRect();
  223.       return this.getBoundingClientRect();
  224.     }
  225.   };
  226.  
  227.   /**
  228.    * Determines the visibility of icons next to the treeItem labels. If set to
  229.    * 'hidden', no space is reserved for icons and no icons are displayed next
  230.    * to treeItem labels. If set to 'parent', folder icons will be displayed
  231.    * next to expandable parent nodes. If set to 'all' folder icons will be
  232.    * displayed next to all nodes. Icons can be set using the treeItem's icon
  233.    * property.
  234.    */
  235.   cr.defineProperty(Tree, 'iconVisibility', cr.PropertyKind.ATTR);
  236.  
  237.   /**
  238.    * Incremental counter for an auto generated ID of the tree item. This will
  239.    * be incremented per element, so each element never share same ID.
  240.    *
  241.    * @type {number}
  242.    */
  243.   var treeItemAutoGeneratedIdCounter = 0;
  244.  
  245.   /**
  246.    * This is used as a blueprint for new tree item elements.
  247.    * @type {!HTMLElement}
  248.    */
  249.   var treeItemProto = (function() {
  250.     var treeItem = cr.doc.createElement('div');
  251.     treeItem.className = 'tree-item';
  252.     treeItem.innerHTML = '<div class=tree-row>' +
  253.         '<span class=expand-icon></span>' +
  254.         '<span class=tree-label></span>' +
  255.         '</div>' +
  256.         '<div class=tree-children></div>';
  257.     treeItem.setAttribute('role', 'treeitem');
  258.     return treeItem;
  259.   })();
  260.  
  261.   /**
  262.    * Creates a new tree item.
  263.    * @param {Object=} opt_propertyBag Optional properties.
  264.    * @constructor
  265.    * @extends {HTMLElement}
  266.    */
  267.   var TreeItem = cr.ui.define(function() {
  268.     var treeItem = treeItemProto.cloneNode(true);
  269.     treeItem.id = 'tree-item-autogen-id-' + treeItemAutoGeneratedIdCounter++;
  270.     return treeItem;
  271.   });
  272.  
  273.   TreeItem.prototype = {
  274.     __proto__: HTMLElement.prototype,
  275.  
  276.     /**
  277.      * Initializes the element.
  278.      */
  279.     decorate: function() {
  280.  
  281.     },
  282.  
  283.     /**
  284.      * The tree items children.
  285.      */
  286.     get items() {
  287.       return this.lastElementChild.children;
  288.     },
  289.  
  290.     /**
  291.      * The depth of the tree item.
  292.      * @type {number}
  293.      */
  294.     depth_: 0,
  295.     get depth() {
  296.       return this.depth_;
  297.     },
  298.  
  299.     /**
  300.      * Sets the depth.
  301.      * @param {number} depth The new depth.
  302.      * @private
  303.      */
  304.     setDepth_: function(depth) {
  305.       if (depth != this.depth_) {
  306.         this.rowElement.style.WebkitPaddingStart = Math.max(0, depth - 1) *
  307.             INDENT + 'px';
  308.         this.depth_ = depth;
  309.         var items = this.items;
  310.         for (var i = 0, item; item = items[i]; i++) {
  311.           item.setDepth_(depth + 1);
  312.         }
  313.       }
  314.     },
  315.  
  316.     /**
  317.      * Adds a tree item as a child.
  318.      * @param {!cr.ui.TreeItem} child The child to add.
  319.      */
  320.     add: function(child) {
  321.       this.addAt(child, 0xffffffff);
  322.     },
  323.  
  324.     /**
  325.      * Adds a tree item as a child at a given index.
  326.      * @param {!cr.ui.TreeItem} child The child to add.
  327.      * @param {number} index The index where to add the child.
  328.      */
  329.     addAt: function(child, index) {
  330.       this.lastElementChild.insertBefore(child, this.items[index]);
  331.       if (this.items.length == 1)
  332.         this.hasChildren = true;
  333.       child.setDepth_(this.depth + 1);
  334.     },
  335.  
  336.     /**
  337.      * Removes a child.
  338.      * @param {!cr.ui.TreeItem} child The tree item child to remove.
  339.      */
  340.     remove: function(child) {
  341.       // If we removed the selected item we should become selected.
  342.       var tree = this.tree;
  343.       var selectedItem = tree.selectedItem;
  344.       if (selectedItem && child.contains(selectedItem))
  345.         this.selected = true;
  346.  
  347.       this.lastElementChild.removeChild(child);
  348.       if (this.items.length == 0)
  349.         this.hasChildren = false;
  350.     },
  351.  
  352.     /**
  353.      * The parent tree item.
  354.      * @type {!cr.ui.Tree|cr.ui.TreeItem}
  355.      */
  356.     get parentItem() {
  357.       var p = this.parentNode;
  358.       while (p && !(p instanceof TreeItem) && !(p instanceof Tree)) {
  359.         p = p.parentNode;
  360.       }
  361.       return p;
  362.     },
  363.  
  364.     /**
  365.      * The tree that the tree item belongs to or null of no added to a tree.
  366.      * @type {cr.ui.Tree}
  367.      */
  368.     get tree() {
  369.       var t = this.parentItem;
  370.       while (t && !(t instanceof Tree)) {
  371.         t = t.parentItem;
  372.       }
  373.       return t;
  374.     },
  375.  
  376.     /**
  377.      * Whether the tree item is expanded or not.
  378.      * @type {boolean}
  379.      */
  380.     get expanded() {
  381.       return this.hasAttribute('expanded');
  382.     },
  383.     set expanded(b) {
  384.       if (this.expanded == b)
  385.         return;
  386.  
  387.       var treeChildren = this.lastElementChild;
  388.  
  389.       if (b) {
  390.         if (this.mayHaveChildren_) {
  391.           this.setAttribute('expanded', '');
  392.           treeChildren.setAttribute('expanded', '');
  393.           cr.dispatchSimpleEvent(this, 'expand', true);
  394.           this.scrollIntoViewIfNeeded(false);
  395.         }
  396.       } else {
  397.         var tree = this.tree;
  398.         if (tree && !this.selected) {
  399.           var oldSelected = tree.selectedItem;
  400.           if (oldSelected && this.contains(oldSelected))
  401.             this.selected = true;
  402.         }
  403.         this.removeAttribute('expanded');
  404.         treeChildren.removeAttribute('expanded');
  405.         cr.dispatchSimpleEvent(this, 'collapse', true);
  406.       }
  407.     },
  408.  
  409.     /**
  410.      * Expands all parent items.
  411.      */
  412.     reveal: function() {
  413.       var pi = this.parentItem;
  414.       while (pi && !(pi instanceof Tree)) {
  415.         pi.expanded = true;
  416.         pi = pi.parentItem;
  417.       }
  418.     },
  419.  
  420.     /**
  421.      * The element representing the row that gets highlighted.
  422.      * @type {!HTMLElement}
  423.      */
  424.     get rowElement() {
  425.       return this.firstElementChild;
  426.     },
  427.  
  428.     /**
  429.      * The element containing the label text and the icon.
  430.      * @type {!HTMLElement}
  431.      */
  432.     get labelElement() {
  433.       return this.firstElementChild.lastElementChild;
  434.     },
  435.  
  436.     /**
  437.      * The label text.
  438.      * @type {string}
  439.      */
  440.     get label() {
  441.       return this.labelElement.textContent;
  442.     },
  443.     set label(s) {
  444.       this.labelElement.textContent = s;
  445.     },
  446.  
  447.     /**
  448.      * The URL for the icon.
  449.      * @type {string}
  450.      */
  451.     get icon() {
  452.       return getComputedStyle(this.labelElement).backgroundImage.slice(4, -1);
  453.     },
  454.     set icon(icon) {
  455.       return this.labelElement.style.backgroundImage = url(icon);
  456.     },
  457.  
  458.     /**
  459.      * Whether the tree item is selected or not.
  460.      * @type {boolean}
  461.      */
  462.     get selected() {
  463.       return this.hasAttribute('selected');
  464.     },
  465.     set selected(b) {
  466.       if (this.selected == b)
  467.         return;
  468.       var rowItem = this.firstElementChild;
  469.       var tree = this.tree;
  470.       if (b) {
  471.         this.setAttribute('selected', '');
  472.         rowItem.setAttribute('selected', '');
  473.         this.reveal();
  474.         this.labelElement.scrollIntoViewIfNeeded(false);
  475.         if (tree)
  476.           tree.selectedItem = this;
  477.       } else {
  478.         this.removeAttribute('selected');
  479.         rowItem.removeAttribute('selected');
  480.         if (tree && tree.selectedItem == this)
  481.           tree.selectedItem = null;
  482.       }
  483.     },
  484.  
  485.     /**
  486.      * Whether the tree item has children.
  487.      * @type {boolean}
  488.      */
  489.     get mayHaveChildren_() {
  490.       return this.hasAttribute('may-have-children');
  491.     },
  492.     set mayHaveChildren_(b) {
  493.       var rowItem = this.firstElementChild;
  494.       if (b) {
  495.         this.setAttribute('may-have-children', '');
  496.         rowItem.setAttribute('may-have-children', '');
  497.       } else {
  498.         this.removeAttribute('may-have-children');
  499.         rowItem.removeAttribute('may-have-children');
  500.       }
  501.     },
  502.  
  503.     /**
  504.      * Whether the tree item has children.
  505.      * @type {boolean}
  506.      */
  507.     get hasChildren() {
  508.       return !!this.items[0];
  509.     },
  510.  
  511.     /**
  512.      * Whether the tree item has children.
  513.      * @type {boolean}
  514.      */
  515.     set hasChildren(b) {
  516.       var rowItem = this.firstElementChild;
  517.       this.setAttribute('has-children', b);
  518.       rowItem.setAttribute('has-children', b);
  519.       if (b)
  520.         this.mayHaveChildren_ = true;
  521.     },
  522.  
  523.     /**
  524.      * Called when the user clicks on a tree item. This is forwarded from the
  525.      * cr.ui.Tree.
  526.      * @param {Event} e The click event.
  527.      */
  528.     handleClick: function(e) {
  529.       if (e.target.className == 'expand-icon')
  530.         this.expanded = !this.expanded;
  531.       else
  532.         this.selected = true;
  533.     },
  534.  
  535.     /**
  536.      * Makes the tree item user editable. If the user renamed the item a
  537.      * bubbling {@code rename} event is fired.
  538.      * @type {boolean}
  539.      */
  540.     set editing(editing) {
  541.       var oldEditing = this.editing;
  542.       if (editing == oldEditing)
  543.         return;
  544.  
  545.       var self = this;
  546.       var labelEl = this.labelElement;
  547.       var text = this.label;
  548.       var input;
  549.  
  550.       // Handles enter and escape which trigger reset and commit respectively.
  551.       function handleKeydown(e) {
  552.         // Make sure that the tree does not handle the key.
  553.         e.stopPropagation();
  554.  
  555.         // Calling tree.focus blurs the input which will make the tree item
  556.         // non editable.
  557.         switch (e.keyIdentifier) {
  558.           case 'U+001B':  // Esc
  559.             input.value = text;
  560.             // fall through
  561.           case 'Enter':
  562.             self.tree.focus();
  563.         }
  564.       }
  565.  
  566.       function stopPropagation(e) {
  567.         e.stopPropagation();
  568.       }
  569.  
  570.       if (editing) {
  571.         this.selected = true;
  572.         this.setAttribute('editing', '');
  573.         this.draggable = false;
  574.  
  575.         // We create an input[type=text] and copy over the label value. When
  576.         // the input loses focus we set editing to false again.
  577.         input = this.ownerDocument.createElement('input');
  578.         input.value = text;
  579.         if (labelEl.firstChild)
  580.           labelEl.replaceChild(input, labelEl.firstChild);
  581.         else
  582.           labelEl.appendChild(input);
  583.  
  584.         input.addEventListener('keydown', handleKeydown);
  585.         input.addEventListener('blur', (function() {
  586.           this.editing = false;
  587.         }).bind(this));
  588.  
  589.         // Make sure that double clicks do not expand and collapse the tree
  590.         // item.
  591.         var eventsToStop = ['mousedown', 'mouseup', 'contextmenu', 'dblclick'];
  592.         eventsToStop.forEach(function(type) {
  593.           input.addEventListener(type, stopPropagation);
  594.         });
  595.  
  596.         // Wait for the input element to recieve focus before sizing it.
  597.         var rowElement = this.rowElement;
  598.         var onFocus = function() {
  599.           input.removeEventListener('focus', onFocus);
  600.           // 20 = the padding and border of the tree-row
  601.           cr.ui.limitInputWidth(input, rowElement, 100);
  602.         };
  603.         input.addEventListener('focus', onFocus);
  604.         input.focus();
  605.         input.select();
  606.  
  607.         this.oldLabel_ = text;
  608.       } else {
  609.         this.removeAttribute('editing');
  610.         this.draggable = true;
  611.         input = labelEl.firstChild;
  612.         var value = input.value;
  613.         if (/^\s*$/.test(value)) {
  614.           labelEl.textContent = this.oldLabel_;
  615.         } else {
  616.           labelEl.textContent = value;
  617.           if (value != this.oldLabel_) {
  618.             cr.dispatchSimpleEvent(this, 'rename', true);
  619.           }
  620.         }
  621.         delete this.oldLabel_;
  622.       }
  623.     },
  624.  
  625.     get editing() {
  626.       return this.hasAttribute('editing');
  627.     }
  628.   };
  629.  
  630.   /**
  631.    * Helper function that returns the next visible tree item.
  632.    * @param {cr.ui.TreeItem} item The tree item.
  633.    * @return {cr.ui.TreeItem} The found item or null.
  634.    */
  635.   function getNext(item) {
  636.     if (item.expanded) {
  637.       var firstChild = item.items[0];
  638.       if (firstChild) {
  639.         return firstChild;
  640.       }
  641.     }
  642.  
  643.     return getNextHelper(item);
  644.   }
  645.  
  646.   /**
  647.    * Another helper function that returns the next visible tree item.
  648.    * @param {cr.ui.TreeItem} item The tree item.
  649.    * @return {cr.ui.TreeItem} The found item or null.
  650.    */
  651.   function getNextHelper(item) {
  652.     if (!item)
  653.       return null;
  654.  
  655.     var nextSibling = item.nextElementSibling;
  656.     if (nextSibling)
  657.       return assertInstanceof(nextSibling, cr.ui.TreeItem);
  658.     return getNextHelper(item.parentItem);
  659.   }
  660.  
  661.   /**
  662.    * Helper function that returns the previous visible tree item.
  663.    * @param {cr.ui.TreeItem} item The tree item.
  664.    * @return {cr.ui.TreeItem} The found item or null.
  665.    */
  666.   function getPrevious(item) {
  667.     var previousSibling = item.previousElementSibling;
  668.     if (previousSibling)
  669.       return getLastHelper(assertInstanceof(previousSibling, cr.ui.TreeItem));
  670.     return item.parentItem;
  671.   }
  672.  
  673.   /**
  674.    * Helper function that returns the last visible tree item in the subtree.
  675.    * @param {cr.ui.TreeItem} item The item to find the last visible item for.
  676.    * @return {cr.ui.TreeItem} The found item or null.
  677.    */
  678.   function getLastHelper(item) {
  679.     if (!item)
  680.       return null;
  681.     if (item.expanded && item.hasChildren) {
  682.       var lastChild = item.items[item.items.length - 1];
  683.       return getLastHelper(lastChild);
  684.     }
  685.     return item;
  686.   }
  687.  
  688.   // Export
  689.   return {
  690.     Tree: Tree,
  691.     TreeItem: TreeItem
  692.   };
  693. });
  694.